#define _GNU_SOURCE
#include <assert.h>
#include <string.h>

#include <object_table.h>
#include <object.h>
#include <line.h>
#include <circle.h>
#include <ellipse.h>
#include <polygon.h>
#include <debug.h>


static struct object_table_entry def_object_table[] = {
  { .type = OBJECT_TYPE_UNDEFINED,
    .name = "<<UNDEFINED>>",
    .ops  = {
      .new = NULL,
      .free = NULL,
      .str = NULL,
      .draw = NULL,
    },
  },

  { .type = OBJECT_TYPE_LINE,
    .name = "line",
    .ops  = {
      .new = (object_t *(*)(void))line_new,
      .free = (void (*)(object_t*))line_free,
      .str = (char* (*)(object_t*))line_str,
      .draw = (void (*)(object_t*, surface_t*))line_draw,
    },
  },

  { .type = OBJECT_TYPE_CIRCLE,
    .name = "circle",
    .ops  = {
      .new = (object_t *(*)(void))circle_new,
      .free = (void (*)(object_t*))circle_free,
      .str = (char* (*)(object_t*))circle_str,
      .draw = (void (*)(object_t*, surface_t*))circle_draw,
    },
  },

  { .type = OBJECT_TYPE_ELLIPSE,
    .name = "ellipse",
    .ops  = {
      .new = (object_t *(*)(void))ellipse_new,
      .free = (void (*)(object_t*))ellipse_free,
      .str = (char* (*)(object_t*))ellipse_str,
      .draw = (void (*)(object_t*, surface_t*))ellipse_draw,
    },
  },

  { .type = OBJECT_TYPE_POLYGON,
    .name = "polygon",
    .ops  = {
      .new = (object_t *(*)(void))polygon_new,
      .free = (void (*)(object_t*))polygon_free,
      .str = (char* (*)(object_t*))polygon_str,
      .draw = (void (*)(object_t*, surface_t*))polygon_draw,
    },
  },
};
static uint16_t object_table_size = \
  sizeof(def_object_table) / sizeof(*def_object_table);

static struct object_table_entry *object_table = def_object_table;


/**
 * Add new object to object's table.
 *
 * @param name an unique name to object.
 * @param ops operations for this object.
 *
 * @return pointer to the entry in the table, please use the returned
 * type for this object.
 */
struct object_table_entry *
add_object_table_entry( const char *name, const struct object_ops *ops )
{
  uint16_t i;
  uint16_t s = object_table_size + 1;
  struct object_table_entry *t;

  for ( i=0; i < object_table_size; i++ )
    if ( strcmp( name, object_table[ i ].name ) == 0 )
      {
	err( "Tried to add a new object, but name \"%s\" already exists.\n",
	     name );
	return NULL;
      }


  if ( object_table == def_object_table ) /* virgin object table */
    t = malloc( sizeof(*t) * s );
  else
    t = realloc( object_table, s );

  if ( t == NULL )
    {
      err( "Could not allocate memory to object_table\n" );
      return NULL;
    }

  object_table = t;
  object_table_size = s;

  t = &object_table[ s - 1 ];

  t->type = s - 1;
  t->name = strdup( name );
  t->ops  = *ops;

  return t;
}

/**
 * Get the object entry from the object's table.
 *
 * The object entry holds information about the object, like functions
 * to act on it, its name and possible other information.
 *
 * @param type the object type.
 *
 * @return pointer to object table entry, with object name, type and
 * operations.
 */
struct object_table_entry *
get_object_table_entry( uint16_t type )
{
  struct object_table_entry *t = NULL;
  if ( type < object_table_size )
    {
      t = &object_table[ type ];
      assert( t->type == type ); /* else it was hacked, so fail */
    }
  return t;
}

/**
 * Restores the original object table, free data.
 *
 * You may register this function on program exit if you add something to
 * object table, otherwise you may get some "memory leaks" notices when
 * you exit. See atexit(3).
 */
void
clear_object_table( void )
{
  if ( object_table != def_object_table )
    {
      uint16_t i = sizeof(def_object_table) / sizeof(*def_object_table);
      for ( ; i < object_table_size; i++ )
	free( object_table[ i ].name );

      free( object_table );
      object_table = def_object_table;
      object_table_size = sizeof(def_object_table) / sizeof(*def_object_table);
    }
}
